
package model.observer;

import java.util.HashMap;
import jgf.Attribute;
import jgf.IGraph;
import jgf.beans.ArcBean;
import jgf.beans.GraphBean;
import jgf.beans.NodeBean;
import jgf.exceptions.arc.ArcException;
import jgf.exceptions.arc.DuplicatedArcException;
import jgf.exceptions.arc.UnexistentArcException;
import jgf.exceptions.attribute.AttributeException;
import jgf.exceptions.attribute.BadAttributeListException;
import jgf.exceptions.attribute.DuplicatedAttributeException;
import jgf.exceptions.attribute.UnexistentAttributeException;
import jgf.exceptions.node.DuplicatedNodeException;
import jgf.exceptions.node.UnexistentNodeException;

/**
 * This class is designed as a container of the model to make it observable
 * in terms of the observer pattern that fits into our MVC structure.<br />
 * It simply stores all the observers and redirect all the calls from the model
 * to them.
 * @author Marcos Garcia Ortega <a href="mailto:thiamath@gmail.com">&lt;thiamath@gmail.com&gt;</a>
 */
public class JGSObservable {

	/** Stores the model to interact with it. */
	private IGraph model;
	/** The list of observers, mapped by its ID number. */
	private HashMap<Integer, JGSObserver> observers;

	/**
	 * Default constructor. It is redirected to the constructor without a model
	 * to fill it later.
	 */
	public JGSObservable(){
		this(null);
	}

	/**
	 * Constructor that stores the graph from the beginning.
	 * @param model The model to store and interact.
	 */
	public JGSObservable(IGraph model){
		this.model = model;
		this.observers = new HashMap<Integer, JGSObserver>();
	}

	public IGraph getGraphInstance(){
		return model;
	}

	/**
	 * Adds a new observer to the model.
	 * @param observer The new observer to add.
	 */
	public void addObserver(JGSObserver observer){
		this.observers.put(observer.getId(), observer);
	}

	/**
	 * Removes an observer from the model.
	 * @param observer The observer to remove.
	 */
	public void removeObserver(JGSObserver observer){
		this.observers.remove(observer.getId());
	}

	/**
	 * Checks if the observable has any observer.
	 * @return A boolean value indicating if the observable object is being
	 * observed by something or not.
	 */
	public boolean hasObservers(){
		return !this.observers.isEmpty();
	}

	/////////////////////////////
	// MODIFIERS OF THE IGRAPH //
	/////////////////////////////

	// <editor-fold defaultstate="collapsed" desc="Observable modifications">
	/**
	 * Loads the stored model into the observers.
	 */
	public void loadGraph() {
		for (Integer obs : observers.keySet()) {
			observers.get(obs).loadGraph(new GraphBean(model));
		}
	}

	/**
	 * Stores a new graph as model and transmit the change to the observers.
	 * @param model The new model to store in the observable container.
	 */
	public void loadGraph(IGraph model){
		this.model = model;
		loadGraph();
	}

	/**
	 * Sets the name of the model overwritting it if necessary and transmits
	 * the change to the observers. If the given name is null,
	 * it will be substituted by an empty String.
	 * @param name The new name of the graph.
	 */
	public void setName(String name) {
		if (name == null) {
			name = "";
		}
		model.setName(name);
		for (Integer obs : observers.keySet()) {
			observers.get(obs).setName(name);
		}
	}

	/**
	 * Inserts a new node into the model and transmits the change
	 * to the observers.
	 * @param node A bean with the information of the node to insert.
	 * @throws BadAttributeListException If the attribute list of the given bean
	 *		do not fit with the graph configuration, this exception will raise.
	 */
	public void insertNode(NodeBean node)
			throws BadAttributeListException {
		Integer nodeID = model.insertNode(node.getLabel());
		try {
			model.updateNode(nodeID, null, node.getAttributes());
			node.setId(nodeID);
		} catch (UnexistentNodeException ex) {
			//Never reached
		}
		for (Integer obs : observers.keySet()) {
			observers.get(obs).insertNode(node);
		}
	}

	/**
	 * Deletes a node from the model and transmits the change
	 * to the observers.
	 * @param node The information of the node to delete.
	 * @throws UnexistentNodeException If the node is not stored into the model,
	 *		this exception will raise.
	 */
	public void deleteNode(NodeBean node)
			throws UnexistentNodeException {
		model.deleteNode(node.getId());
		for (Integer obs : observers.keySet()) {
			observers.get(obs).deleteNode(node);
		}
	}

	/**
	 * Updates the node into the graph, changing the label and the attributes
	 * and transmits the change to the observers.
	 * @param node The new information of the node.
	 * @throws UnexistentNodeException If the node is not stored into the model,
	 *		this exception will raise.
	 * @throws BadAttributeListException If the attribute list of the given bean
	 *		do not fit with the graph configuration, this exception will raise.
	 */
	public void updateNode(NodeBean node)
			throws UnexistentNodeException,
			BadAttributeListException {
		model.updateNode(node.getId(), node.getLabel(), node.getAttributes());
		for (Integer obs : observers.keySet()) {
			observers.get(obs).updateNode(node);
		}
	}

	/**
	 * Sets the given node as source in the model and transmits the change
	 * to the observers.
	 * @param node The node that will become source.
	 * @throws DuplicatedNodeException If the node to insert into the sources
	 *		is already there, then this exception will raise.
	 * @throws UnexistentNodeException If the node to insert into the sources
	 *		do not exist, then this exception will raise.
	 */
	public void setSource(NodeBean node)
			throws DuplicatedNodeException,
			UnexistentNodeException {
		model.setSource(node.getId());
		for (Integer obs : observers.keySet()) {
			observers.get(obs).setSource(node);
		}
	}

	/**
	 * Usets the state of source to the node in the model and transmits the
	 * change to the observers.
	 * @param node The node that will be fired from the sources list.
	 * @throws UnexistentNodeException If the node to remove from the sources
	 *		is not there, then this exception will raise.
	 */
	public void unsetSource(NodeBean node)
			throws UnexistentNodeException {
		model.unsetSource(node.getId());
		for (Integer obs : observers.keySet()) {
			observers.get(obs).unsetSource(node);
		}
	}

	/**
	 * Sets the given node as sink in the model and transmits the change
	 * to the observers.
	 * @param node The node that will become sink.
	 * @throws DuplicatedNodeException If the node to insert into the sources
	 *		is already there, then this exception will raise.
	 * @throws UnexistentNodeException If the node to insert into the sources
	 *		do not exist, then this exception will raise.
	 */
	public void setSink(NodeBean node)
			throws DuplicatedNodeException,
			UnexistentNodeException {
		model.setSink(node.getId());
		for (Integer obs : observers.keySet()) {
			observers.get(obs).setSink(node);
		}
	}

	/**
	 * Usets the state of sink to the node in the model and transmits the
	 * change to the observers.
	 * @param node The node that will be fired from the sinks list.
	 * @throws UnexistentNodeException If the node to remove from the sinks
	 *		is not there, then this exception will raise.
	 */
	public void unsetSink(NodeBean node)
			throws UnexistentNodeException {
		model.unsetSink(node.getId());
		for (Integer obs : observers.keySet()) {
			observers.get(obs).unsetSink(node);
		}
	}

	/**
	 * Inserts a new arc into the model and transmits the change to
	 * the observers.
	 * @param arc The new arc to insert. Must be filled with the node IDs.
	 * @throws ArcException If there is an generic problem, as null pointers
	 *		etc. this exception will raise.
	 * @throws DuplicatedArcException If you try to insert an arc between two
	 *		nodes that have already an arc and the graph is not multiple,
	 *		this exception will raise.
	 * @throws DuplicatedNodeException If you try to insert an arc that have
	 *		the same node as 'from' and 'to' and the graph is not a pseudograph,
	 *		this exception will raise.
	 * @throws UnexistentNodeException If one of the given nodes to define
	 *		the arc do not exist on the graph, this exception will raise.
	 */
	public void insertArc(ArcBean arc)
			throws ArcException,
			DuplicatedArcException,
			DuplicatedNodeException,
			UnexistentNodeException {
		Integer arcID = model.insertArc(arc.getFromId(), arc.getToId(), arc.getLabel());
		arc.setId(arcID);
		for (Integer obs : observers.keySet()) {
			observers.get(obs).insertArc(arc);
		}
	}

	/**
	 * Removes the arc from the model and transmits the change to the observers.
	 * @param arc The arc to remove from the model.
	 * @throws UnexistentArcException If the given ID number is not mapped
	 *		in the arc tree, this exception will raise.
	 */
	public void deleteArc(ArcBean arc)
			throws UnexistentArcException {
		model.deleteArc(arc.getId());
		for (Integer obs : observers.keySet()) {
			observers.get(obs).deleteArc(arc);
		}
	}

	/**
	 * Updates the information of the arc and transmits the change
	 * to the observers.
	 * @param arc The new information of the arc.
	 * @throws UnexistentArcException If the given ID number do not correspond
	 *		with any arc in the graph, this exception will raise.
	 * @throws BadAttributeListException If any of the given attributes
	 *		do not match with the attributes assigned to arcs on the graph,
	 *		this exception will raise.
	 */
	public void updateArc(ArcBean arc)
			throws UnexistentArcException,
			BadAttributeListException {
		model.updateArc(arc.getId(), arc.getLabel(), arc.getAttributes());
		for (Integer obs : observers.keySet()) {
			observers.get(obs).updateArc(arc);
		}
	}

	/**
	 * Adds a new default attribute for the nodes and transmits the change
	 * to the observers.
	 * @param attribute The new attribute for the nodes.
	 * @throws DuplicatedAttributeException If the selected name for
	 *		the attribute is already present on the model,
	 *		this exception will raise.
	 */
	public void addDefaultNodeAttribute(Attribute attribute)
			throws DuplicatedAttributeException {
		model.addDefaultNodeAttribute(attribute);
		for (Integer obs : observers.keySet()) {
			observers.get(obs).addDefaultNodeAttribute(attribute);
		}
	}

	/**
	 * Removes a default attribute for nodes from the model and transmits
	 * the change to the observers.
	 * @param attribute The attribute to delete.
	 * @throws UnexistentAttributeException If the attribute does not exist
	 *		for nodes on this graph, this exception will raise.
	 */
	public void removeDefaultNodeAttribute(Attribute attribute)
			throws UnexistentAttributeException {
		model.removeDefaultNodeAttribute(attribute.getName());
		for (Integer obs : observers.keySet()) {
			observers.get(obs).removeDefaultNodeAttribute(attribute);
		}
	}

	/**
	 * Changes the information of a default attribute on the model and transmits
	 * the change to the observers.
	 * @param previousName The name of the attribute to change.
	 * @param newValue The new attribute that will substitute the previous one.
	 * @throws AttributeException If any of the given attributes is null,
	 *		this exception will raise.
	 * @throws DuplicatedAttributeException If the new name already exists
	 *		on the list of default attributes for nodes, this exception will raise.
	 * @throws UnexistentAttributeException If the previous name does not exist
	 *		on the list of default attributes for nodes, this exception will raise.
	 */
	public void updateDefaultNodeAttribute(String previousName, Attribute newValue)
			throws AttributeException,
			DuplicatedAttributeException,
			UnexistentAttributeException {
		model.updateDefaultNodeAttribute(previousName, newValue);
		for (Integer obs : observers.keySet()) {
			observers.get(obs).updateDefaultNodeAttribute(previousName, newValue);
		}
	}

	/**
	 * Adds a new default attribute for the arcs and transmits the change
	 * to the observers.
	 * @param attribute The new attribute to add.
	 * @throws DuplicatedAttributeException If the selected name for
	 *		the attribute is already present on the model,
	 *		this exception will raise.
	 */
	public void addDefaultArcAttribute(Attribute attribute)
			throws DuplicatedAttributeException {
		model.addDefaultArcAttribute(attribute);
		for (Integer obs : observers.keySet()) {
			observers.get(obs).addDefaultArcAttribute(attribute);
		}
	}

	/**
	 * Removes a default attribute for arcs from the model and transmits
	 * the change to the observers.
	 * @param attribute The attribute to delete.
	 * @throws UnexistentAttributeException If the attribute does not exist
	 *		for nodes on this graph, this exception will raise.
	 */
	public void removeDefaultArcAttribute(Attribute attribute)
			throws UnexistentAttributeException {
		model.removeDefaultArcAttribute(attribute.getName());
		for (Integer obs : observers.keySet()) {
			observers.get(obs).removeDefaultArcAttribute(attribute);
		}
	}

	/**
	 * Changes the information of a default attribute on the model and transmits
	 * the change to the observers.
	 * @param previousName The name of the attribute to change.
	 * @param newValue The ne attribute that will substitute the previous one.
	 * @throws AttributeException If any of the given attributes is null,
	 *		this exception will raise.
	 * @throws DuplicatedAttributeException If the new name already exists
	 *		on the list of default attributes for nodes, this exception will raise.
	 * @throws UnexistentAttributeException If the previous name does not exist
	 *		on the list of default attributes for nodes, this exception will raise.
	 */
	public void updateDefaultArcAttribute(String previousName, Attribute newValue)
			throws AttributeException,
			DuplicatedAttributeException,
			UnexistentAttributeException {
		model.updateDefaultArcAttribute(previousName, newValue);
		for (Integer obs : observers.keySet()) {
			observers.get(obs).updateDefaultArcAttribute(previousName, newValue);
		}
	}
	// </editor-fold>
}
